package main

import (
	"context"
	"github.com/gin-gonic/gin"
	"github.com/go-redis/redis/v9"
	"github.com/google/uuid"
	"gorm.io/driver/mysql"
	"gorm.io/gorm"
	"log"
	"net/http"
)

type Order struct {
	OrderId           int64 `gorm:"primaryKey"`
	AccumulativeTotal float64
	Version           int64
}

func (Order) TableName() string {
	return "t_order"
}

// 加锁lua代码
const LOCK_LUA = "if redis.call(\"setnx\", KEYS[1], KEYS[2]) == 1 then\n    return redis.call(\"pexpire\", KEYS[1], KEYS[3])\nelse\n    return 0\nend"

// 解锁lua代码
const UNLOCK_LUA = "if redis.call(\"get\",KEYS[1]) == KEYS[2] then\n    return redis.call(\"del\",KEYS[1])\nelse\n    return -1\nend"
const LOCK_KEY = "lock_key"

// 分布式锁 代码
func main() {
	//建立redis连接
	rdb := redis.NewClient(&redis.Options{
		Addr:     "mysql4:6379",
		Password: "", // no password set
		DB:       0,  // use default DB
	})
	//建立mysql连接
	dsn := "root:mWwdsqw1s2x3x*@tcp(mysql3:3306)/mytest?charset=utf8mb4&parseTime=True&loc=Local"
	db, err := gorm.Open(mysql.Open(dsn), &gorm.Config{})
	if err != nil {
		log.Println("mysql数据库连接失败,", err.Error())
		return
	}
	//创建gin路由
	r := gin.Default()
	r.GET("/update", func(c *gin.Context) {
		// 加锁
		ctx := context.Background()
		value := uuid.NewString()
		lockResult, _ := rdb.Eval(ctx, LOCK_LUA, []string{LOCK_KEY, value, "5000"}).Result()
		for lockResult != int64(1) { // 加锁失败
			// 重新尝试加锁
			lockResult, _ = rdb.Eval(ctx, LOCK_LUA, []string{LOCK_KEY, value, "5000"}).Result()
		}
		//加锁成功
		//开启事务
		tx := db.Begin()
		var orderBefore Order
		//读取数据
		tx.Raw("select order_id, accumulative_total, version from t_order where order_id = ?", 1).Scan(&orderBefore)
		//内存中加一后更新
		tx.Exec("update t_order set accumulative_total = ? where order_id = ?",
			orderBefore.AccumulativeTotal+1.0, 1)
		//解锁
		unlockResult, _ := rdb.Eval(ctx, UNLOCK_LUA, []string{LOCK_KEY, value}).Result()
		if unlockResult != int64(1) { //解锁失败，可能是方法太耗时，key过期了
			//回滚
			tx.Rollback()
			c.JSON(http.StatusServiceUnavailable, gin.H{
				"message": "no",
			})
		} else { //解锁成功
			//提交
			tx.Commit()
			c.JSON(http.StatusOK, gin.H{
				"message": "ok",
			})
		}
	})
	r.Run(":8080") // 监听并在 0.0.0.0:8080 上启动服务
}
